﻿using System;
using System.IO;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
using QuickTest.Services;
using QuickTest.Services.TextRetrieval;

namespace QuickTest.Tests.Services.TextRetrieval
{
    [TestFixture]
    public class EmbeddedResourceTextRetrieverTests
    {
        internal const string EmbeddedResourcePath = "QuickTest.Tests, QuickTest.Tests.Services.TextRetrieval.Artifacts.EmbeddedResource.txt";
        internal const string EmbeddedResourceText = "This is test text";
        private const string EmbeddedResourcePathWithSpaces = " QuickTest.Tests, QuickTest.Tests.Services.TextRetrieval.Artifacts.EmbeddedResource.txt ";
        private const string EmbeddedResourcePathWithDifferentAssemblyName = "QuickTest.Core,QuickTest.Tests.Artifacts.EmbeddedResource.txt";
        private const string EmbeddedResourcePathWithDifferentResourceName = "QuickTest.Tests,EmbeddedResource.txt";
        private const string NonAssemblyQualifedEmbeddedResourcePath = "QuickTest.Tests.Services.TextRetrieval.Artifacts.EmbeddedResource.txt";
        private const string AssemblyName = "QuickTest.Tests";

        [Test]
        public void Can_Get_Text()
        {
            var settings = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePath);
            var textRetriever = new EmbeddedResourceTextRetriever(settings);

            Assert.That(textRetriever.GetText(), Is.EqualTo(EmbeddedResourceText), "Should have returned same text as in QuickTest.Tests.Artifacts.EmbeddedResource.txt.");
        }

        [Test]
        public void Should_Use_Cached_Text_If_GetText_Called_Twice()
        {
            var settings = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePath);
            var textRetriever = new FakeEmbeddedResourceTextRetriever(settings);

            textRetriever.ResourceFileAccessed = false;
            textRetriever.GetText();
            Assert.That(textRetriever.ResourceFileAccessed, Is.True, "Should not have used cached text, so should have accessed resource directly.");

            textRetriever.ResourceFileAccessed = false;
            textRetriever.GetText();
            Assert.That(textRetriever.ResourceFileAccessed, Is.False, "Should have used cached text, so should not have accessed resource directly.");
        }

        [Test]
        public void Can_Get_Cached_Text()
        {
            var settings = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePath);
            var textRetriever = new EmbeddedResourceTextRetriever(settings);

            //call to force cache to load.
            textRetriever.GetText();

            Assert.That(textRetriever.GetText(), Is.EqualTo(EmbeddedResourceText), "Should have returned same text as in QuickTest.Tests.Artifacts.EmbeddedResource.txt.");
        }

        [Test]
        public void Can_Get_Text_When_AssemblyQualifiedName_Has_Spaces()
        {
            var settings = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePathWithSpaces);
            var textRetriever = new EmbeddedResourceTextRetriever(settings);

            Assert.That(textRetriever.GetText(), Is.EqualTo(EmbeddedResourceText), "Should have returned same text as in QuickTest.Tests.Artifacts.EmbeddedResource.txt.");
        }

        [Test]
        public void Constructor_Throws_ArgumentException_If_Empty_Settings()
        {
            EmbeddedResourceTextRetrieverSettings settings;
            Exception ex = TestUtils.AssertThrows<ArgumentException>(() => new EmbeddedResourceTextRetriever(settings), "ArgumentException should have been thrown.");

            Assert.That(ex.Message, Is.EqualTo("TextRetriever settings cannot be empty"), "Exception should be 'TextRetriever settings cannot be empty'");
        }

        [Test]
        public void GetText_Throws_Exception_If_Resource_File_Is_Not_Found()
        {
            var settings = new EmbeddedResourceTextRetrieverSettings("QuickTest.Tests, NoFile.txt");
            var textRetriever = new EmbeddedResourceTextRetriever(settings);

            Exception ex = TestUtils.AssertThrows<ServiceException>(() => textRetriever.GetText(), "Exception should have been thrown.");

            Assert.That(ex.Message, Is.EqualTo("Resource not found in given assembly"), "Exception should be 'Resource not found in given assembly'");
        }

        #region EmbeddedResourceTextRetrieverSettingsTests

        [Test]
        [Category("Settings")]
        public void Constructor_Throws_ArgumentException_If_Null_AssemblyQualifiedResourceName()
        {
            Exception ex = TestUtils.AssertThrows<ArgumentException>(
                () => new EmbeddedResourceTextRetrieverSettings(null), "ArgumentException should have been thrown.");

            Assert.That(ex.Message, Is.EqualTo("AssemblyQualifedResourceName is required"), "Exception should be 'AssemblyQualifedResourceName is required'");
        }

        [Test]
        [Category("Settings")]
        public void Constructor_Throws_ArgumentException_If_Empty_AssemblyQualifiedResourceName()
        {
            Exception ex = TestUtils.AssertThrows<ArgumentException>(
                () => new EmbeddedResourceTextRetrieverSettings(string.Empty), "ArgumentException should have been thrown.");

            Assert.That(ex.Message, Is.EqualTo("AssemblyQualifedResourceName is required"), "Exception should be 'AssemblyQualifedResourceName is required'");
        }

        [Test]
        [Category("Settings")]
        public void Constructor_Throws_ArgumentException_If_Non_AssemblyQualifiedResourceName()
        {
            Exception ex = TestUtils.AssertThrows<ArgumentException>(
                () => new EmbeddedResourceTextRetrieverSettings(NonAssemblyQualifedEmbeddedResourcePath), "ArgumentException should have been thrown.");

            Assert.That(ex.Message, Is.EqualTo("ResourceName is not assembly qualified"), "Exception should be 'ResourceName is not assembly qualified'");
        }

        [Test]
        [Category("Settings")]
        public void Equals_False_If_Passed_Null()
        {
            var settings1 = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePath);

            Assert.That(settings1.Equals(null), Is.False, "Equals should be false");
        }

        [Test]
        [Category("Settings")]
        public void Equals_False_If_Passed_A_Different_Type()
        {
            var settings1 = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePath);

            Assert.That(settings1.Equals(new Object()), Is.False, "Equals should be false");
        }

        [Test]
        [Category("Settings")]
        public void Equals_False_If_AssemblyName_Different()
        {
            var settings1 = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePath);
            var settings2 = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePathWithDifferentAssemblyName);

            Assert.That(settings1.Equals(settings2), Is.False, "Equals should be false");
        }

        [Test]
        [Category("Settings")]
        public void Equals_False_If_ResourceName_Different()
        {
            var settings1 = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePath);
            var settings2 = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePathWithDifferentResourceName);

            Assert.That(settings1.Equals(settings2), Is.False, "Equals should be false");
        }

        [Test]
        [Category("Settings")]
        public void Equals_True_If_AssemblyName_And_Resourcename_The_Same()
        {
            var settings1 = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePath);
            var settings2 = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePath);

            Assert.That(settings1.Equals(settings2), Is.True, "Equals should be true");
        }

        [Test]
        [Category("Settings")]
        public void GetHashCode_Returns_Hashcode_Of_AssemblyName_And_ResourceName()
        {
            var settings = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePath);

            Assert.That(settings.GetHashCode(), Is.EqualTo(AssemblyName.GetHashCode() +
                                                           NonAssemblyQualifedEmbeddedResourcePath.GetHashCode()), "GetHashCode should be concatenated hashcode of AssemblyName and ResourceName.");
        }

        [Test]
        [Category("Settings")]
        public void Empty_True_If_Uninitialised()
        {
            EmbeddedResourceTextRetrieverSettings settings;

            Assert.That(settings == EmbeddedResourceTextRetrieverSettings.Empty, Is.True, "Empty should be true");
        }

        [Test]
        [Category("Settings")]
        public void Empty_False_If_Uninitialised()
        {
            var settings = new EmbeddedResourceTextRetrieverSettings(EmbeddedResourcePath);

            Assert.That(settings == EmbeddedResourceTextRetrieverSettings.Empty, Is.False, "Empty should be false");
        }

        #endregion
    }

    internal class FakeEmbeddedResourceTextRetriever : EmbeddedResourceTextRetriever
    {
        public FakeEmbeddedResourceTextRetriever(EmbeddedResourceTextRetrieverSettings settings) : base(settings)
        {
        }

        public bool ResourceFileAccessed;

        protected override Stream GetResourceStream()
        {
            ResourceFileAccessed = true;
            return base.GetResourceStream();
        }
    }
}